기존 데이터 참조 방식은:
&
immutable / shared reference (read)&mut
mutable reference / unique reference (read/write)데이터를 조작하는 또 다른 방법 Cell
mut
을 쓰지 않고 데이터를 변환할 수 있음 (interior mutability)!Sync
쓰레드간 싱크를 맞추지 않음.UnsafeCell<T>
는 러스트에서 유일하게 mutable하지 않은 인스턴스의 값을 변형할 수 있는 방법임Cell<T>
, RefCell<T>
모두 내부적으로 UnsafeCell<T>
를 이용함. let mut x = 9; // 11을 원할때 아래는 thread safe하지 않음
thread 1 {
x+=1;
}
thread 2 {
x+=1;
}
또 다른 방법:
- `RefCell`
- `Mutex`
- `RwLock`
use std::cell::Cell;
#[derive(Debug)]
struct PhoneModel {
company_name: String,
model_name: String,
screen_size: f32,
memory: usize,
date_issued: u32,
on_sale: Cell<bool>
}
fn main() {
// struct의 instance가 가진 값 중 하나만 변경하고 싶을 때
// instance를 mut로 선언하고 싶지 않음
// 변경을 원하는 field에 Cell 타입을 지정한다.
let super_phone_3000 = PhoneModel {
company_name: "ABC".to_string(),
model_name: "ABC S".to_string(),
screen_size: 5.0,
memory: 4_000_000,
date_issued: 2020,
on_sale: Cell::new(true)
};
println!("{super_phone_3000:?}");
super_phone_3000.on_sale.set(false);
println!("{super_phone_3000:?}");
}
결과값:
PhoneModel { company_name: "ABC", model_name: "ABC S", screen_size: 5.0, memory: 4000000, date_issued: 2020, on_sale: Cell { value: true } }
PhoneModel { company_name: "ABC", model_name: "ABC S", screen_size: 5.0, memory: 4000000, date_issued: 2020, on_sale: Cell { value: false } }
use std::cell::Cell;
fn main() {
let c = Cell:new(String::from("I am a String"));
c.set(String::from("I am a String?"));
let s = c.get(); // copy type일때만 사용할 수 있음, returns copy of contained value
let s = c.into_inner(); // unwrap Cell
}
use std::cell::RefCell;
// panic은 unwind stack
#[derive(Debug)]
struct User {
id: u32,
year: u32,
name: String,
active: RefCell<bool>
}
fn main() {
let u = User {
id: 1,
year: 2020,
name: "U1".to_string(),
active: RefCell::new(true)
};
let f_ref = u.active.borrow_mut();
let s_ref = u.active.borrow_mut(); // panic
}
thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:22:26
use std::cell::RefCell;
// panic은 unwind stack
#[derive(Debug)]
struct User {
id: u32,
year: u32,
name: String,
active: RefCell<bool>
}
fn main() {
let u = User {
id: 1,
year: 2020,
name: "U1".to_string(),
active: RefCell::new(true)
};
println!("{u:?}"); // true
let mut f_ref = u.active.borrow_mut();
println!("{u:?}"); // borrowed
*f_ref = false;
println!("{u:?}"); // borrowed
drop(f_ref);
println!("{u:?}"); // false
}
try_borrow_mut
로 안전하게 참조 가져올 수 있음use std::cell::RefCell;
fn main() {
let rc = RefCell::new(String::from("I am a String"));
println!("{rc:?}");
*rc.borrow_mut() = String::from("Change it"); // RefMut type
println!("{rc:?}"); // 바로 변경하면 borrowed 대상이 없어서 바로 반환됨
match rc.try_borrow_mut() { // 역시 반환 대상없음
Ok(mut r) => *r = String::from("Ok"),
Err(e) => println!("Error: {e}")
};
println!("{rc:?}"); // Ok
let block = rc.borrow_mut(); // 가져감
match rc.try_borrow_mut() {
Ok(mut r) => *r = String::from("Ok"),
Err(e) => println!("Error: {e}") // Error: already borrowed
};
println!("{rc:?}"); // borrowed runtime checked
}
BorrowFlag
R/W 상태를 runtime에서 확인하기 위한 플래그 Cell
에서 사용한다.
외부 모듈을 사용할 때 trait가 있는 경우
use std::cell::{Cell, RefCell};
// 외부 모듈이 이런 trait을 구현하도록 만들어져있을 때
// &mut self가 아니라서 값을 변형할 수 없다면
trait XTrait {
fn cool_fun(&self);
}
// 이렇게 struct에서 interior mutability를 부여한 뒤
#[derive(Debug)]
struct User {
id: u32,
count: Cell<u32>
}
impl XTrait for User {
// 이런식으로 값을 변형할 수 있다.
fn cool_fun(&self) {
let count = self.count.get();
self.count.set(count+1);
println!("Counting...");
}
}
fn main() {
let u = User {
id: 123,
count: Cell::new(0)
};
for _ in 0..20 {
u.cool_fun();
}
}
러스트 초보자는 Rc를 도배할 수 있음. T에 대한 shared ownership을 heap에 저장하여 제공한다. (shared_ptr) Rc의 clone은 힙의 같은 위치를 가르키는 새로운 포인터를 만든다.
use std::rc::Rc;
fn ts(input: String) {
}
fn ts2(input: Rc<String>) {
println!("{input}");
}
fn ts3(input: Rc<String>) {
println!("{input}");
}
fn ts4(input: &String) {
println!("{input}");
}
fn main() {
let str = "Hello there".to_string();
//ts0(str); // 함수로 소유가 넘어가서 끝남
ts(str); // X
let str2 = "Test2".to_string();
ts(str2.clone()); // clone은 anti-type. 안쓰는게 좋다
ts(str2);
let str3 = Rc::new("Rc String".to_string());
// 소유자가 하나 더 생긴다?
ts2(Rc::clone(&str3)); // 메모리를 거의 쓰지 않음
ts3(Rc::clone(&str3));
let str4 = "SharedPtr".to_string();
ts4(&str4);
ts4(&str4);
let rc = Rc::new(());
// method-call syntax
let rc2 = rc.clone();
// Fully qualified syntax
let rc3 = Rc::clone(&rc);
}
예시
도구는 주인에게 속한다. 여러 도구는 한 주인에게 속할 수 있다. unique ownership으로는 이를 구현할 수 없다.
use std::rc::Rc;
struct Owner {
name: String,
// ...other fields
}
struct Gadget {
id: i32,
// Gadget인스턴스가 여럿이라 할 때
// owner값을 동일한 Owner 인스턴스로 지정하려면 소유권 문제를 해결해야함.
owner: Rc<Owner>,
// ...other fields
}
fn main() {
// 참조가 여럿인 Owner instance를 만든다.
let gadget_owner: Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
}
);
// Rc를 clone함으로써 참조의 개수만 늘려서 저렴하게 지정이 가능함
let gadget1 = Gadget {
id: 1,
owner: Rc::clone(&gadget_owner),
}; // 그냥 shared ref를 주면? 이 인스턴스가 점거를 하고 있나?
let gadget2 = Gadget {
id: 2,
owner: Rc::clone(&gadget_owner),
};
// Rc owner instance를 메모리에서 해제
drop(gadget_owner);
// 여전히 출력할 수 있다. Rc만 drop이 된 상태라서 참조가 남아있음
// `Rc<Owner>` 는 알아서 `Owner`로 deferenced된다
println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);
println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);
// 이 시점에서 완전히 해제된다.
}
🤔 lifetime과의 비교
struct Owner {
name: String,
// ...other fields
}
struct Gadget<'a> {
id: i32,
// lifetime
owner: &'a Owner,
// ...other fields
}
fn main() {
let gadget_owner: Owner = Owner {
name: String::from("TEST"),
};
let gadget1 = Gadget {
id: 1,
owner: &gadget_owner,
};
let gadget2 = Gadget {
id: 2,
owner: &gadget_owner,
};
println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);
println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);
}
Rc를 써도 되지만, Rust의 장점인 zero-cost abstraction를 통한 빠른 속도는 lifetime과 reference를 이해했을 때 가능하다. C/C++와 같은 언어에서는 작성하기 엄두가 안나는 코드(메모리릭)를 러스트로 작성이 가능하다. rc와 같은 smart pointer는 runtime 비용이있다.
Rc를 왜 써야하나?
Rc는 strong_count
만약 위의 코드에서 Owner가 Gadget를 참조하게 되면 순환참조가 걸려서 메모리 누수가 발생한다. 이럴 때는 Weak
pointer를 사용해야한다. Weak 참조
Weak
은 non-owning reference이다. 값을 실제로 초기화 하지 않고 메모리 공간만 할당해놓는다. (값을 보장할 수 없고(덮어씌워질 수 있다?) upgrade로 메모리에 값이 남아있는지 확인 가능하다.
러스트는 태생적으로 순환참조가 어렵도록 설계되었다. 순환참조를 만드려면 둘중하나는 mutable이어야한다. Rc를 사용하면 애초에 shared reference만 주기 때문에 mutable하지 않다. 이걸 정 해야한다면 interior mutability를 활용한다. (RefCell)
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let a = Rc::new(RefCell::new("Interior Mutability".to_string()));
// Rc는 알아서 deref되니까
*a.borrow_mut() = "Now".to_string();
println!("{}", a.borrow_mut()); // Now
}